#
#	parse_address.py
#
#	David Janes
#	2009.02.03
#

import sys
import os
import os.path
import pprint
import re
import types
import cStringIO

import bm_locales
import bm_data_streets
import vcard
import mfdict

from bm_log import Log

break_re = """
	(?:
		\s*,\s*
		|
		\s+-\s+
		|
		\s*:\s+
	)
	"""
break_rex = re.compile(break_re, re.I|re.DOTALL|re.VERBOSE)

def add_points(map, amount):
	return
	map["points"] = map.get("points", 0) + amount

def scale_points(map, amount):
	return
	map["points"] = map.get("points", 0) * amount

def post_area_code(match, parsed):
	area_code_map = bm_locales.area_code(match.group("area_code"))

	country = area_code_map.get("country")
	if country:
		parsed["_COUNTRY"] = country.upper()

	state = area_code_map.get("state")
	if state:
		parsed["_STATE"] = state.upper()

	add_points(parsed, 5)

direction_map = {
	"e" : "East",
	"w" : "West",
	"n" : "North",
	"s" : "South",
	"e." : "East",
	"w." : "West",
	"n." : "North",
	"s." : "South",
}

def post_street(match, parsed):
	street_list = match.group("all").split()
	if not street_list:
		return

	v = direction_map.get(street_list[-1].lower())
	if v:
		street_list[-1] = v

	typ = match.group("type")
	if typ:
		try:
			position = street_list.index(typ)
			street_list[position] = bm_data_streets.street_map.get(typ.lower().rstrip("."), typ)
		except ValueError, x:
			return

	parsed["STREET"] = " ".join(street_list)

def post_country(match, parsed):
	country = match.group("country")
	if not country or len(country) == 2:
		return

	country = country.strip().lower()

	for code, name in bm_locales.country_map.iteritems():
		if name.lower() == country:
			parsed["COUNTRY"] = code.upper()

def post_province(match, parsed):
	state = match.group("state")
	if not state or len(state) == 2:
		return

	state = state.strip().lower()

	for code, name in bm_locales.ca_province_map.iteritems():
		if name.lower() == state:
			parsed["STATE"] = code.upper()

def post_state(match, parsed):
	state = match.group("state")
	if not state or len(state) == 2:
		return

	state = state.strip().lower()

	for code, name in bm_locales.us_state_map.iteritems():
		if name.lower() == state:
			parsed["STATE"] = code.upper()


rules = [
	{
		"name" : "Canadian Postal Code",
		"re" :
			"""
			\s([a-z]\d[a-z])[-\s]*(\d[a-z]\d)\s
			""",
		"parsed" : {
			"_COUNTRY" : "CA",
			"POSTAL" : "\\1 \\2",
		},
		"is_rule_needed" : True,
	},
	{
		"name" : "Canadian Province",
		"re" :
			"""
			\s(?P<state>
			""" + "|".join(bm_locales.ca_province_map.keys()) + """
			|
			""" + "|".join(bm_locales.ca_province_map.values()) + """
			)\s
			""",
		"parsed" : {
			"_COUNTRY" : "CA",
			"STATE" : "\\1",
		},
		"post" : post_province,
	},
	{
		"name" : "US State + ZIP",
		"re" :
			"""
			\s
			(
				(?P<state>
				""" + "|".join(bm_locales.us_state_map.keys()) + """
				|
				""" + "|".join(bm_locales.us_state_map.values()) + """
				)
				\s+
				(?P<postal>
					\d{5}
				)
			)
			\s
			""",
		"parsed" : {
			"_COUNTRY" : "US",
			"STATE" : "\\g<state>",
			"POSTAL" : "\\g<postal>",
		},
		"post" : post_state,
	},
	{
		"name" : "US State",
		"re" :
			"""
			\s
			(
				(?P<state>
				""" + "|".join(bm_locales.us_state_map.keys()) + """
				|
				""" + "|".join(bm_locales.us_state_map.values()) + """
				)
			)
			\s
			""",
		"parsed" : {
			"_COUNTRY" : "US",
			"STATE" : "\\g<state>",
		},
		"post" : post_state,
	},
	{
		"name" : "US Zip",
		"re" :
			"""
			\s
			(?P<postal>
				\d{5}
			)
			\s
			""",
		"parsed" : {
			"_COUNTRY" : "US",
			"POSTAL" : "\\g<postal>",
		},
		"is_rule_needed" : True,
	},
	{
		"name" : "Countries",
		"re" :
			"""
			\s
			(?P<country>
			""" + "|".join(bm_locales.country_map.keys())  + """
			|
			""" + "|".join(bm_locales.country_map.values())  + """
			)
			\s
			""",
		"parsed" : {
			"COUNTRY" : "\\g<country>",
		},
		"post" : post_country,
	},
	{
		"name" : "North American Phone",
		"re" :
			"""
			\s(
				[(]?(?P<area_code>\d{3})\s*[-).]\s*(\d{3})\s*[-.]\s*(\d{4})
				(
					\s*
					(x|ext|extension)?
					\s*
					(\d*)
				)?
			)\s
			""",
		"parsed" : {
			"PHONE" : "(\\g<area_code>) \\3-\\4",
		},
		"post" : post_area_code,
		"is_rule_needed" : True,
	},
	{
		"name" : "Street Name",
		"re" :
			"""
			\s
			(?P<all>
				(?P<number>\d+(-\d+)?([a-z])?)
				\s+
				(.*)
				\s+
				(?P<type>(""" + "|".join(bm_data_streets.street_map.keys()) + """)[.]?)
				(
					\s+
					(e|w|n|s|east|north|south|west)
					[.]?
				)?
			)
			(
				\s+
				[(][^)]*[)]
			)?
			\s
			""",
		"parsed" : {
			"STREET" : "\\g<all>",
		},
		"post" : post_street,
	},
	{
		"name" : "P.O. Boxes",
		"is_full_only" : True,
		"re" :
			"""
			\s
			(?P<all>
				P\.O\.\s+Box.*
				|PO\s+Box.*
			)
			\s
			""",
		"parsed" : {
			"POBOX" : "\\g<all>",
		},
	},
	{
		"name" : "Leading Extensions",
		"is_full_only" : True,
		"re" :
			"""
			\s
			(?P<all>
				|Unit\s+.*
				|Building\s+.*
			)
			\s
			""",
		"parsed" : {
			"EXTENDED" : "\\g<all>",
		},
	},
	{
		"name" : "End Extensions",
		"is_full_only" : True,
		"re" :
			"""
			\s
			(?P<all>
				\s+Limited
				|\s+L[.]?t[.]?d[.]?
				|\s+Inc[.]?
			)
			\s
			""",
		"parsed" : {
			"ORGANIZATION" : "\\g<all>",
		},
	},
	{
		"name" : "Organization Types ",
		"is_full_only" : True,
		"re" :
			"""
			\s
			(?P<all>
				.*\s
				(
					University
					|Institute
				)
				\s.*
			)
			\s
			""",
		"parsed" : {
			"ORGANIZATION" : "\\g<all>",
		},
	},
	{
		"name" : "Organization Types, end only",
		"is_full_only" : True,
		"re" :
			"""
			\s
			(?P<all>
				Department\s.*
				|
				.*\sRelations
			)
			\s
			""",
		"parsed" : {
			"DEPARTMENT" : "\\g<all>",
		},
	},
	{
		"name" : "FAX",
		"is_full_only" : True,
		"re" :
			"""
			(
				^\s
				(
					fax|facsimile|fx[.]?
				)
				\s
			|
				\s
				[(]?
				(
					fax|facsimile|fx[.]?
				)
				[)]?
				\s$
			)
			""",
		"parsed" : {
			"phone-advice" : "FAX",
		},
	},
	{
		"name" : "Phone",
		"is_full_only" : True,
		"re" :
			"""
			(
				^\s
				(
					tel[.]?|telephone|phone|ph[.]?
				)
				\s
			|
				\s
				[(]?
				(
					tel[.]?|telephone|phone|ph[.]?
				)
				[)]?
				\s$
			)
			""",
		"parsed" : {
			"phone-advice" : "PHONE",
		},
	},
	{
		"name" : "EMail Addresses",
		"re" :
			"""
			^\s*
			(email:)?
			(?P<email>
				[0-9a-z][-+._0-9a-z]*
				@
				[0-9a-z][-._0-9a-z]*
				[.]
				 (  com                               #     TLD
				 |  edu                               #
				 |  biz                               #
				 |  gov                               #
				 |  in(?:t|fo)                        #     .int or .info
				 |  mil                               #
				 |  net                               #
				 |  org                               #
				 |  museum                            #
				 |  aero                              #
				 |  coop                              #
				 |  name                              #
				 |  pro                               #
				 |  [a-z][a-z]                        #     two-letter country codes
				 )                                    #
			)
			\s*$
			""",
		"parsed" : {
			"EMAIL" : "\\g<email>",
		},
	},
]

for rule in rules:
	rule["rex"] = re.compile(rule["re"], re.I|re.DOTALL|re.VERBOSE)

class TextAddressParser:
	def __init__(self, hint_work_home = '', hint_has_name = True):
		self.linelists = []
		self.hint_has_name = hint_has_name

		self.hint_work_home = hint_work_home
		assert(self.hint_work_home in [ '', vcard.Home, vcard.Work, ])

	def FeedMany(self, document):
		self.linelists = []

		try:
			if type(document) in types.StringTypes:
				sin = cStringIO.StringIO(document.encode('utf-8'))
			else:
				sin = document

			self.linelists = list(self.ReadMany(sin))
		finally:
			try: sin.close()
			except: pass

	def FeedOne(self, document):
		self.linelists = []

		try:
			if type(document) in types.StringTypes:
				sin = cStringIO.StringIO(document.encode('utf-8'))
			else:
				sin = document

			self.linelists = [ self.ReadOne(sin) ]
		finally:
			try: sin.close()
			except: pass
			
	def Iterate(self):
		def make_key(*av):
			return	".".join(filter(None, av))

		for linelist in self.linelists:
			self.ParseLineList(linelist)
			result = self.Result()

			mfd = mfdict.mfdict()
			for ( key, value ) in result:
				ckey = None
				if key == "NAME":
					ckey = make_key(vcard.FN)
				elif key == "CITY":
					ckey = make_key(vcard.ADR, vcard.Locality, self.hint_work_home)
				elif key == "COUNTRY":
					ckey = make_key(vcard.ADR, vcard.CountryName, self.hint_work_home)
				elif key == "EXTENDED":
					ckey = make_key(vcard.ADR, vcard.ExtendedAddress, self.hint_work_home)
				elif key == "POSTAL":
					ckey = make_key(vcard.ADR, vcard.PostalCode, self.hint_work_home)
				elif key == "STATE":
					ckey = make_key(vcard.ADR, vcard.Region, self.hint_work_home)
				elif key == "STREET":
					ckey = make_key(vcard.ADR, vcard.StreetAddress, self.hint_work_home)
				elif key == "POBOX":
					ckey = make_key(vcard.ADR, vcard.PostOfficeBox, self.hint_work_home)
				elif key == "PHONE":
					ckey = make_key(vcard.TEL, vcard.Voice, self.hint_work_home)
				elif key == "FAX":
					ckey = make_key(vcard.TEL, vcard.Fax, self.hint_work_home)
				elif key == "ORGANIZATION":
					ckey = make_key(vcard.OrganizationName)
				elif key == "DEPARTMENT":
					ckey = make_key(vcard.OrganizationUnit)
				elif key == "EMAIL":
					ckey = make_key(vcard.Email, self.hint_work_home)
				else:
					Log("UNKNOWN", key = key)
					continue

				mfd[ckey] = value

			yield mfd

		raise	StopIteration

	def ParseLineList(self, linelist):
		self.lines = list(linelist)
		self.lines.reverse()

		self.ukeys = {}
		self.cooked_result = []

		self.Parse()
		self.CookFullMatches()
		self.CookEndMatches()
		self.CookMiddleMatches()
		self.CookFindCity()
		self.CookNames()
		self.CookPhone()
		self.CookEMails()
		self.CreateResult()
		self.ResolveAmbiguityCountry()
		self.ResolveAmbiguityState()
		self.Fatten()

	def Result(self):
		return	self.cooked_result

	def CreateResult(self):
		self.cooked_result = []

		for atom in self.atoms:
			parsed_list = atom["parsed"]
			if not parsed_list:
				continue

			for parsed in parsed_list:
				for key in parsed.keys():
					if key[:1] == '_':
						parsed[key[1:]] = parsed[key]

				for key in self.CardKeys(parsed):
					v = parsed[key]
					if not v:
						continue

					t = ( key, parsed[key] )

					if t not in self.cooked_result:
						self.cooked_result.append(t)

	def Parse(self):
		self.atoms = []

		line_number = 0
		for line in self.lines:
			self.ParseLine(line_number, line)
			line_number += 1

		#
		#	Single line address are broken up as multiline address
		#
		self.single_line = False

		if line_number == 1:
			line_number = 0
			for atom in self.atoms:
				atom['@line'] = line_number
				line_number += 1

			self.single_line = True

	def ParseLine(self, line_number, line):
		line_atoms = break_rex.split(line)
		line_atoms.reverse()

		for part in line_atoms:
			self.atoms.append({
				"value" : part,
				"@line" : line_number,
				"parsed" : [],
			})

	def CardKeys(self, map):
		ukeys = []

		if type(map) == types.ListType:
			map_list = map
		else:
			map_list = [ map ]

		for map in map_list:
			for key in map.keys():
				if re.match("^[-A-Z]+$", key) and key not in ukeys:
					ukeys.append(key)

		return	ukeys

	def AllKeys(self, map):
		ukeys = []

		if type(map) == types.ListType:
			map_list = map
		else:
			map_list = [ map ]

		for map in map_list:
			for key in map.keys():
				if key not in ukeys:
					ukeys.append(key)

		return	ukeys

	def CookRuleMatch(self, rule, match):
		oparsed = rule["parsed"]
		nparsed = {}
		for key, value in oparsed.iteritems():
			if type(value) in types.StringTypes:
				nparsed[key] = match.expand(value)
			else:
				nparsed[key] = value

		post = rule.get("post")
		if post:
			post(match, nparsed)

		return	nparsed

	def AddParsed(self, atom, parsed):
		"""This not only adds the 'parsed' information but keeps
		track of what we have collected already.
		"""
		atom["parsed"].append(parsed)

		for ukey in self.CardKeys(parsed):
			self.ukeys[ukey] = 1

	def IsRuleNeeded(self, rule):
		"""Rule Needed means we're still looking for this rule
		"""
		if rule.get("is_rule_needed"):
			return	True

		rule_ukeys = self.CardKeys(rule["parsed"])
		if not rule_ukeys:
			return	True

		for ukey in rule_ukeys:
			if not self.ukeys.get(ukey):
				return	True

		return	False

	def CookFullMatches(self):
		"""Pass 0 looks for exact matches
		"""
		for atom in self.atoms:
			for rule in rules:
				if not self.IsRuleNeeded(rule):
					continue

				match = rule["rex"].search(" %s " % atom["value"])
				# print atom["value"], rule["name"], match
				if not match:
					continue

				if match.start() != 0 or match.end() != (len(atom["value"]) + 2):
					continue

				self.AddParsed(atom, self.CookRuleMatch(rule, match))
				# pprint.pprint(atom)

	def CookEndMatches(self):
		"""Pass 1 looks for partial matches at the end of lines and rewrites the atoms
		"""
		ai = -1
		while True:
			#
			#	Go through each atom; this gives us the opportunity
			#	to rewrite the array as we go along
			#
			ai += 1
			if ai == len(self.atoms):
				break

			#
			#	We ignore Atoms that have been processed
			#
			atom = self.atoms[ai]
			if atom["parsed"]:
				continue

			atom_value = atom["value"]

			for rule in rules:
				if not self.IsRuleNeeded(rule):
					continue
				if rule.get("is_full_only"):
					continue

				#
				#	Look for an end match
				#
				match = rule["rex"].search(" %s " % atom_value)
				if not match:
					continue

				if match.end() != (len(atom["value"]) + 2):
					continue

				#
				#	At this point we know that we're going to
				#	stop processing following rules, rewrite
				#	(and reprocess) the current atom, and insert a new one
				#
				new_atom = dict(atom)
				new_atom["value"] = atom_value[:match.start()].strip()
				new_atom["parsed"] = []
				new_atom["is-added"] = True

				self.atoms.insert(ai, new_atom)
				ai -= 1

				#
				#	Now cook this atom
				#
				self.AddParsed(atom, self.CookRuleMatch(rule, match))
				break


	def CookMiddleMatches(self):
		"""Like Pass 1, but can find matches in the middle
		"""
		ai = -1
		while True:
			#
			#	Go through each atom; this gives us the opportunity
			#	to rewrite the array as we go along
			#
			ai += 1
			if ai == len(self.atoms):
				break

			#
			#	We ignore Atoms that have been processed
			#
			atom = self.atoms[ai]
			if atom["parsed"]:
				continue

			atom_value = atom["value"]

			for rule in rules:
				if not self.IsRuleNeeded(rule):
					continue
				if rule.get("is_full_only"):
					continue

				#
				#	Look for an end match
				#
				match = rule["rex"].search(" %s " % atom_value)
				if not match:
					continue

				#
				#	At this point we know that we're going to
				#	stop processing following rules, rewrite
				#	(and reprocess) the current atom, and insert a new one
				#
				pre_atom = dict(atom)
				pre_atom["value"] = atom_value[:match.start()].strip()
				pre_atom["parsed"] = []
				pre_atom["is-added"] = True

				self.atoms.insert(ai, pre_atom)

				post_atom = dict(atom)
				post_atom["value"] = atom_value[match.end()-1:].strip()
				post_atom["parsed"] = []
				post_atom["is-added"] = True

				self.atoms.insert(ai + 2, post_atom)

				ai -= 1

				#
				#	Now cook this atom
				#
				self.AddParsed(atom, self.CookRuleMatch(rule, match))
				break

	def CookFindCity(self):
		seen_country = False
		atom_after_country = None

		seen_state = False
		atom_after_state = None

		seen_postal = False
		atom_after_postal = None

		for atom in self.atoms:
			parsed_list = atom["parsed"]
			if not parsed_list:
				atom_value = atom["value"]
				if re.search("\d", atom_value):	# cities don't have numbers
					continue
				if atom_value.endswith("."):	# cities aren't abbrvs.
					continue

				if not atom_after_country and seen_country:
					atom_after_country = atom
				if not atom_after_state and seen_state:
					atom_after_state = atom
				if not atom_after_postal and seen_postal:
					atom_after_postal = atom
			else:
				ukeys = self.CardKeys(parsed_list)

				if "COUNTRY" in ukeys:
					seen_country = True
				if "STATE" in ukeys:
					seen_state = True
				if "POSTAL" in ukeys:
					seen_state = True

		atom_after = atom_after_state or atom_after_postal or atom_after_country
		if not atom_after:
			return

		atom_after["parsed"].append({
			"CITY" : atom_after["value"],
		})


	def CookNames(self):
		"""This attempts to understand free text
		"""
		ratoms = list(self.atoms)
		ratoms.reverse()

		name_atoms = []
		for atom in ratoms:
			parsed_list = atom["parsed"]
			if parsed_list:
				ukeys = self.CardKeys(parsed_list)
				if "COUNTRY" in ukeys: break
				if "STATE" in ukeys: break
				if "POSTAL" in ukeys: break
				if "CITY" in ukeys: break
			elif self.hint_has_name:
				name_atoms.append(atom)

		if self.single_line:
			for ni in xrange(len(name_atoms)):
				name_atom = name_atoms[ni]
				if ni == 0 :
					name_atom["parsed"].append({
						"NAME" : name_atom["value"],
					})
				else:
					name_atom["parsed"].append({
						"ORGANIZATION" : name_atom["value"],
					})
		else:
			lines_used = {}

			for ni in xrange(len(name_atoms)):
				name_atom = name_atoms[ni]
				line = name_atom["@line"]

				if lines_used.get(line):
					continue

				lines_used[line] = 1

				if ni == 0:
					name_atom["parsed"].append({
						"NAME" : self.lines[line],
					})
				else:
					name_atom["parsed"].append({
						"ORGANIZATION" : self.lines[line],
					})

	def CookEMails(self):
		pass
		
	def CookPhone(self):
		phone_atoms = []
		count = -1
		for atom in self.atoms:
			count += 1

			parsed_list = atom["parsed"]
			if not parsed_list:
				continue

			if "PHONE" in self.CardKeys(parsed_list):
				atom["@index"] = count
				phone_atoms.append(atom)

		# pprint.pprint(phone_atoms)

		#
		#	Try to find phone descriptors on the same line
		#
		conflict = False
		conflict_next = 0
		conflict_prev = 0

		for phone_atom in phone_atoms:
			next_atom = self.GetAtom(phone_atom["@index"] + 1)
			if not self.single_line and next_atom.get("@line") != phone_atom["@line"]:
				next_atom = None

			if next_atom:
				self.FlattenAtom(next_atom)

			if next_atom and not next_atom["flat"].get("phone-advice"):
				next_atom = None

			prev_atom = self.GetAtom(phone_atom["@index"] - 1)
			if not self.single_line and prev_atom.get("@line") != phone_atom["@line"]:
				prev_atom = None

			if prev_atom:
				self.FlattenAtom(prev_atom)

			if prev_atom and not prev_atom["flat"].get("phone-advice"):
				prev_atom = None

			#
			#
			#
			if next_atom: conflict_next += 1
			if prev_atom: conflict_prev += 1

			if next_atom and prev_atom:
				conflict = True
				continue

			if not next_atom and not prev_atom:
				continue

			adjacent_atom = next_atom or prev_atom

			for parsed in phone_atom["parsed"]:
				old_phone_value = parsed.get("PHONE")
				new_phone_key = adjacent_atom["flat"]["phone-advice"]
				if old_phone_value and new_phone_key != "PHONE":
					parsed[new_phone_key] = old_phone_value
					del parsed["PHONE"]
					break

	def Fatten(self):
		country = None
		state = None

		count = -1
		for ( key, value ) in self.cooked_result:
			count += 1

			if key == "COUNTRY":
				country_index = count
				country = value
			elif key == "STATE":
				state_index = count
				state = value

		if country and len(country) == 2:
			self.cooked_result[country_index] = ( 
				"COUNTRY",
				bm_locales.country_to_name(country)
			)

		if state:
			if country == "CA":
				self.cooked_result[state_index] = ( 
					"STATE",
					bm_locales.ca_province_map.get(state.lower(), state)
				)
			elif country == "US":
				self.cooked_result[state_index] = ( 
					"STATE",
					bm_locales.us_state_map.get(state.lower(), state)
				)


	def ResolveAmbiguityCountry(self):
		self.ResolveAmbiguity("COUNTRY")

	def ResolveAmbiguityState(self):
		states = map(lambda ( k, v ): v, filter(lambda ( k, v ): k == "STATE", self.cooked_result))
		if len(states) <= 1:
			return

		# make sure the state is consistent with the country

		# more filtering...
		self.ResolveAmbiguity("STATE")

	def ResolveAmbiguity(self, rkey):
		rvalues = map(lambda ( k, v ): v, filter(lambda ( k, v ): k == rkey, self.cooked_result))
		if len(rvalues) <= 1:
			return

		#
		#	Make a sum of all the times we found a rvalue
		#
		count_map = {}

		for atom in self.atoms:
			for parsed in atom["parsed"]:
				for key, value in parsed.iteritems():
					if value not in rvalues:
						continue

					if key == rkey:
						count_map[value] = count_map.get(value, 0.0) + 1.0
					elif key == "_rkey":
						count_map[value] = count_map.get(value, 0.0) + 0.5

		#
		#	Find the best
		#
		best_count = -1
		best_rvalue = None
		for rvalue, count in count_map.iteritems():
			if count > best_count:
				best_count = count
				best_rvalue = rvalue

		#
		#	And remove the rest
		#
		self.cooked_result = filter(
			lambda ( k, v ): k != rkey or v == best_rvalue,
			self.cooked_result)

	def GetAtom(self, index, otherwise = None):
		if index < 0 or index >= len(self.atoms):
			return	(otherwise != None) and otherwise or {}
		else:
			return	self.atoms[index]

	def FlattenAtom(self, atom):
		flat = {}
		for parsed in atom["parsed"]:
			flat.update(parsed)

		atom["flat"] = flat

	def ReadMany(cls, fin):
		results = []
		result = None

		while True:
			line = fin.readline()
			if not line:
				break

			line = line.strip()
			if not line:
				result = None
				continue

			if result == None:
				result = []
				results.append(result)

			result.append(line)

		return	results

	ReadMany = classmethod(ReadMany)

	def ReadOne(cls, fin):
		result = []

		while True:
			line = fin.readline()
			if not line:
				break

			line = line.strip()
			if not line:
				continue

			result.append(line)

		return	result

	ReadOne = classmethod(ReadOne)

def parse(text, hint_work_home = ""):
	parser = TextAddressParser(hint_work_home = hint_work_home)
	parser.FeedOne(text)

	for hd in parser.Iterate():
		return	hd

	return	mfdict.mfdict()

def raw_parse(text):
	resultd = {}
	for rd in rules:
		rule_rex = rd['rex']
		rule_match = rule_rex.search(text)
		if not rule_match:
			continue

		for key, value in rd['parsed'].iteritems():
			if resultd.get(key):
				continue

			resultd[key] = rule_rex.sub(value, rule_match.group(0))

	return	resultd

if __name__ == '__main__':
	pp = pprint.PrettyPrinter(width = 80)

	from optparse import OptionParser

	parser = OptionParser()
	parser.add_option(
		"", "--work",
		action = "store_true",
		default = False,
		dest = "is_work",
		help = "Addresses are work addresses")
	parser.add_option(
		"", "--home",
		action = "store_true",
		default = False,
		dest = "is_home",
		help = "Addresses are home addresses")
	parser.add_option(
		"", "--one",
		action = "store_true",
		default = False,
		dest = "is_one",
		help = "Each input contains one address (i.e. don't use space to delimit addresses)")
	parser.add_option(
		"", "--n",
		type = "int",
		default = sys.maxint,
		dest = "n",
		help = "Print at most <N> addresses per file")

	(options, args) = parser.parse_args()

	if not args: args = [ "-" ]

	hint_work_home = ""
	if options.is_work: hint_work_home = vcard.Work
	elif options.is_home: hint_work_home = vcard.Home

	parser = TextAddressParser(hint_work_home = hint_work_home)
	for file in args:
		if file == "-":
			fin = sys.stdin
		else:
			fin = open(file, 'rb')

		if options.is_one:
			parser.FeedOne(fin)
		else:
			parser.FeedMany(fin)

		count = -1
		for card in parser.Iterate():
			count += 1
			if count >= options.n:
				break

			pp.pprint(card)
